/*
 *    builder.cpp - functions for creating llvm-specific nodes
 *
 *    2008, Zalunin Pavel
 */

#include "builder.h"

#define INCRVAR(name) const_counter++; sprintf(str,"%s%d", name,const_counter);
#define STRPLUS(one,two) strcat(one,two)

char str[25];

Function *buildMainFunction(Module *mod){
  Constant* cons = mod->getOrInsertFunction("main",
					    IntegerType::get(32),
					    NULL);
  Function* mainFunc = cast<Function>(cons);
  mainFunc->setCallingConv(CallingConv::C);
  return mainFunc;
}

Value *buildLocalIntInit(BasicBlock *block, string name, int value){
  Value* var = new AllocaInst(Type::Int32Ty,(Value*)0, 4, name, block);
  new StoreInst(ConstantInt::get(Type::Int32Ty, value), var,block);
  return var;
}

Value *buildGlobalIntInit(Module *mod, string name, int value){
  return new GlobalVariable(Type::Int32Ty, 
		            false, 
		            GlobalValue::ExternalLinkage,
		            ConstantInt::get(Type::Int32Ty, value),
		            name,
		            mod);
}

Value *buildGlobalStrInit(Module *mod, string name, string value){ 
  vector<Constant*> values;
  for(int i=0;i<value.size();i++){
    values.push_back(ConstantInt::get(IntegerType::get(8), value[i]));
  }
  new GlobalVariable(ArrayType::get(IntegerType::get(8),value.size()), 
   		            false, 
   		            GlobalValue::InternalLinkage,
   		            ConstantArray::get(ArrayType::get(IntegerType::get(8),value.size()), values),
   		            name,
   		            mod);  
  return NULL;
}

Value *createStringConstant(Module *mod, string value){ 
  vector<Constant*> values;
  for(int i=0;i<value.size();i++){
    values.push_back(ConstantInt::get(IntegerType::get(8), value[i]));
  }
  values.push_back(ConstantInt::get(IntegerType::get(8), 0));
  const_counter++;
  char str[25];
  sprintf(str,".str%d", const_counter);
  GlobalVariable *var = new GlobalVariable(ArrayType::get(IntegerType::get(8),value.size()+1), 
   		                           false, 
					   GlobalValue::InternalLinkage,
					   ConstantArray::get(ArrayType::get(IntegerType::get(8),value.size()+1), values),
					   string(str),
					   mod);  
  return var;
}

Value *invokeFunction(BasicBlock *mod, string name){
  return NULL;
}


void makeStandardDefines(Module *mod){
  Constant* putsc = mod->getOrInsertFunction("puts",
			  		     IntegerType::get(32),
					     PointerType::get(IntegerType::get(8)),
					     NULL);

  Constant* strcatc = mod->getOrInsertFunction("strcat",
			    		       PointerType::get(IntegerType::get(8)),
					       PointerType::get(IntegerType::get(8)),
					       PointerType::get(IntegerType::get(8)),
					       NULL);

  Constant* strlenc = mod->getOrInsertFunction("strlen",
			    		       IntegerType::get(32),
					       PointerType::get(IntegerType::get(8)),
					       NULL);

  Constant* memcpy = mod->getOrInsertFunction("llvm.memcpy.i32",
                                             Type::VoidTy,
                                             PointerType::get(IntegerType::get(8)),
                                             PointerType::get(IntegerType::get(8)),
                                             IntegerType::get(32),
                                             IntegerType::get(32),
                                             NULL);
}


Value *createStringValue(Value *value, BasicBlock *block){  
  if(!isa<GlobalVariable>(value)){
    return value;
  }else{
      vector <Value*> vars;
      vars.push_back(ConstantInt::get(Type::Int64Ty, 0));
      vars.push_back(ConstantInt::get(Type::Int64Ty, 0));
      INCRVAR("str")
      GetElementPtrInst *ptr = new GetElementPtrInst(value, vars.begin(), vars.end(), str, block);
      return ptr;
  }
}

Value *callDef(Function *func, vector<Value*> params, BasicBlock *block){  
  INCRVAR("tmp")
  CallInst *callInst = new CallInst (func, params.begin(), params.end(),str,  block);
  return callInst;
}

Value *allocMemoryForString(BasicBlock *block){
  INCRVAR("tmp");
  Value *alloc = new AllocaInst(ArrayType::get(Type::Int8Ty, 2048),(Value*)0, 16, str, block);  
  return alloc;
}

Value *makePtrForString(Value *val, BasicBlock *block){
  vector <Value*> vars;
  vars.push_back(ConstantInt::get(Type::Int32Ty, 0));
  vars.push_back(ConstantInt::get(Type::Int32Ty, 0));
  INCRVAR("str")
  GetElementPtrInst *ptrInst = new GetElementPtrInst(val, vars.begin(), vars.end(), str, block);
  return ptrInst;
}

//TODO: calculate concatenated string length
Value *createStringConcat(Value *val1, Value* val2, BasicBlock *block, Module *mod){
  val1 = createStringValue(val1, block);
  val2 = createStringValue(val2, block);
  Value *allocInst = allocMemoryForString(block);
  Value *ptrTmp = makePtrForString(allocInst, block);

  Function *strlenf = mod->getFunction("strlen");
  vector<Value*> slparam;
  slparam.push_back(val2);
  INCRVAR("tmp")
  Value* callLen = new CallInst(strlenf, slparam.begin(), slparam.end(), STRPLUS(str,".len") , block);  

  Function *memcpyf = mod->getFunction("llvm.memcpy.i32");
  vector<Value*> mcpar;
  mcpar.push_back(ptrTmp);
  mcpar.push_back(val1);
  mcpar.push_back(callLen);
  mcpar.push_back(ConstantInt::get(Type::Int32Ty, 1));
  new CallInst(memcpyf, mcpar.begin(), mcpar.end(), "", block);

  Function *strcatf = mod->getFunction("strcat");
  vector<Value*> scparam;
  scparam.push_back(ptrTmp);
  scparam.push_back(val2);
  new CallInst(strcatf, scparam.begin(), scparam.end(), "", block);
  return ptrTmp;
}

Value *storeInteger(int value, Module *mod){
  // INCRVAR(".int")
  // GlobalVariable *var = new GlobalVariable(IntegerType::get(32), 
  //  		                           false, 
  // 					   GlobalValue::InternalLinkage,
  // 					   ConstantInt::get(Type::Int32Ty,value),
  // 					   str,
  // 					   mod);  

  return ConstantInt::get(Type::Int32Ty,value);
}

Value *buildAddOperation(Value *v1, Value *v2, BasicBlock *block){
  LLVMBuilder builder(block);
  INCRVAR(".int.op")
  return builder.CreateAdd(v1, v2, str);
}

Value *buildSubOperation(Value *v1, Value *v2, BasicBlock *block){
  LLVMBuilder builder(block);
  INCRVAR(".int.op")
  return builder.CreateSub(v1, v2, str);
}

Value *buildMulOperation(Value *v1, Value *v2, BasicBlock *block){
  LLVMBuilder builder(block);
  INCRVAR(".int.op")
  return builder.CreateMul(v1, v2, str);
}

Value *buildDivOperation(Value *v1, Value *v2, BasicBlock *block){
  LLVMBuilder builder(block);
  INCRVAR(".int.op")
  return builder.CreateSDiv(v1, v2, str);  
}

LoopNode *buildForHeadBlock(BasicBlock *block, Value *times){

  Function *function = block->getParent();
  BasicBlock *condBlock = new BasicBlock("for_cond", function);
  BasicBlock *entryBlock = new BasicBlock("for_entry", function);
  BasicBlock *mainBlock = new BasicBlock("main_block", function);
  //for head block
  BasicBlock *forHead = new BasicBlock("for_head", function);
  forHead->moveAfter(block);
  INCRVAR("ifi");
  Value *alloci = new AllocaInst(IntegerType::get(32),(Value*)0, 4, str, forHead);    
  new StoreInst(ConstantInt::get(IntegerType::get(32), 0), alloci,false, 4, forHead);

  new BranchInst(condBlock, forHead);

  //for entry block
  entryBlock->moveAfter(forHead);  
  
  if(block->getTerminator()==NULL)
    new BranchInst(forHead, block);  


  //for condition block  
  condBlock->moveAfter(entryBlock);
  INCRVAR("tmp");
  Value *ilval = new LoadInst(alloci, str, false, 4, condBlock);

  Value *cond = new ICmpInst(ICmpInst::ICMP_SLE, ilval, times, str, condBlock);

  /* new BranchInst(entryBlock, mainBlock, cond, condBlock); */

  LoopNode *node = new LoopNode();  
  node->condBlock = condBlock;
  node->cond = cond;
  node->entryBlock = entryBlock;
  node->incrVar = alloci;  
  node->nextBlock = mainBlock;
  //creating new main block for operations after loop
  mainBlock->moveAfter(condBlock);
  return node;
}

void buildIncrInstr(LoopNode *node, BasicBlock *block){
  INCRVAR("tmp")
  Value *nival = new LoadInst (node->incrVar, str,false, 4, block);
  LLVMBuilder buildbb(block);
  INCRVAR("tmp");
  Value *inci = buildbb.CreateAdd(nival, ConstantInt::get(IntegerType::get(32), 1), str);
  new StoreInst(inci,node->incrVar, false, 4, block);  
}

BasicBlock *getNextBlock(BasicBlock *cur){
  Function *func = cur->getParent();
  if(cur!=func->end()){ 
     Function::iterator blocks = cur;
     blocks++;
     return blocks;
  } 
  return NULL;
}

Value *allocMemoryForString(BasicBlock *block, string name, int size){
  Value *alloc = new AllocaInst(ArrayType::get(Type::Int8Ty, size),(Value*)0, 16, name, block);  
  return alloc;
}

Value* initStringLocalVar(string name, BasicBlock *block, Value *expression, Module *mod){
  Value* alloc = allocMemoryForString(block, name, 2048);  
  Value* allocP = makePtrForString(alloc, block);
  Value *ptr = makePtrForString(expression, block);

  Function *strlenf = mod->getFunction("strlen");
  vector<Value*> slparam;
  slparam.push_back(ptr);
  INCRVAR("tmp")
  Value* callLen = new CallInst(strlenf, slparam.begin(), slparam.end(), STRPLUS(str,".len") , block);  

  Function *memcpyf = mod->getFunction("llvm.memcpy.i32");
  vector<Value*> mcpar;
  mcpar.push_back(allocP);
  mcpar.push_back(ptr);
  mcpar.push_back(callLen);
  mcpar.push_back(ConstantInt::get(Type::Int32Ty, 1));
  new CallInst(memcpyf, mcpar.begin(), mcpar.end(), "", block);

  return allocP;
}


Function *createDefineTest(string name, Module *module){
  
  return NULL;
}
